#ifndef NETWORK_PROTOCOL_H
#define NETWORK_PROTOCOL_H

#include <boost/bind.hpp>
#include <boost/smart_ptr.hpp>
#include <boost/asio.hpp>
#include <boost/thread.hpp>
#include <boost/thread/locks.hpp>
#include "NetworkPacket.h"
#include <Log.h>
#include <map>
#include <set>


using boost::asio::ip::tcp;
typedef boost::shared_ptr<tcp::socket> socket_ptr;

/**
 * Maaksymalny rozmiar pakietu jaki obsługuje system.
 */
#define BUFFOR_LENGTH (1024*256)

namespace CSNetworkLib
{
	/**
	 *	Enum używany do kodowania wyjątków.
	 */
	enum EExceptionId
	{
		EException_Unknown,             //!< EException_Unknown
		EException_WrongArgument,       //!< EException_WrongArgument
		EException_AuthenticationFailed,//!< EException_AuthenticationFailed
		EException_InvalidPath,         //!< EException_InvalidPath
		EException_StorageOffline,      //!< EException_StorageOffline
		EException_InvalidType,         //!< EException_InvalidType
		EException_FileNotExits,        //!< EException_FileNotExits
		EException_FolderNotExits,      //!< EException_FolderNotExits
		EException_FileExits,           //!< EException_FileExits
		EException_FolderExits,         //!< EException_FolderExits
		EException_InvalidOperation,    //!< EException_InvalidOperation
        EException_OperationFailed//!< EException_OperationFailed
	};

	/**
	 * Klasa protokołu, obsługuje połączenia, przychodzące pakiety i wywołuje zarejestrowane handlery.
	 */
	class NetworkProtocol
	{
		public:
		/**
		 * Typ metody handlera, metoda obsługująca pakiet musi zwracać void i jako parametr przyjmować typ "const NetworkPacket &".
		 */
		typedef void (NetworkProtocol::*handlerPtr)(const NetworkPacket &);
		private:
			/**
			 *	Klasa wewnętrzna używana przez metodę NetworkProtocol::SendWait
			 */
			class TResponseWaitPacket
			{
				public:
					NetworkPacket *response;
					bool processed;
					boost::mutex t_mut;
					boost::condition_variable t_wait;

					/**
					 *	Konstruktor podstawowy.
					 */
					TResponseWaitPacket()
					{
						response = NULL;
						processed = false;
					}

					/**
					 * Metoda czeka na odpowiedź na dopiero co wysłany pakiet.
					 * @param ms ilość milisekund jaką metoda ma oczekiwać na odpowiedź.
					 */
					inline void Wait(uint32_t ms)
					{
						FUNCTION_TRACK
						boost::unique_lock<boost::mutex> lock(t_mut);
						try{
						t_wait.timed_wait(lock, boost::get_system_time() + boost::posix_time::milliseconds(ms));
						}catch(const std::exception &e)
						{
							ERR("Exception: %s", e.what());
						}
					}

					/**
					 * Metoda powiadamia wszystkich oczekujących iż pakiet na który czekali przyszedł i powinien zostać przetworzony.
					 */
					inline void NotifyAll()
					{
						FUNCTION_TRACK
						{
							boost::lock_guard<boost::mutex> lock(t_mut);
							processed = true;
						}
						t_wait.notify_one();
					}
			};

			/**
			 * Podstawowy interfejs obsługujący pakiety
			 */
			class Delegate {
				public:
					/**
					 * Metoda wołana przez NetworkProtocol celem obsłużenia pakietu danego typu.
					 * @param packet pakiet do obsłużenia.
					 */
					  virtual void Invoke(const NetworkPacket &packet) = 0;
					  Delegate(){}
					  virtual ~Delegate(){}
			};

			typedef Delegate* delegate_ptr;

			/**
			 *	Klasa template implementująca intefejs Delegate.
			 */
			template<typename T>
			class TypeDelegate : public Delegate
			{
			public:
				/**
				 * Konstruktor podstawowy 
				 * @param t klasa protokołu (this)
				 * @param pfn wskaźnik na adres metody obsługującej dany pakiet.
				 * @param async informacja czy pakiet ma być obsługiwany synchronicznie czy asynchronicznie.
				 */
			   TypeDelegate(T *t, void (T::*pfn)(const NetworkPacket &), bool async) : m_t(*t), m_pfn(pfn), m_async(async) {}
			   /**
			    * Destruktor domyślny
			    */
			   virtual ~TypeDelegate(){}

			   /**
			    * Metoda wywołująca obsługę pakietu przez metodę w obiekcie podanym w konstruktorze.
			    * @param packet pakiet który ma zostać obsłużony
			    */
			   void Invoke(const NetworkPacket &packet)
			   {
					FUNCTION_TRACK
					if (!m_async)
					{
						try{
						 (m_t.*m_pfn)(packet);
						} catch(std::exception &e)
						{
							ERR("EXCEPTION(PacketID:%d) : %s", packet.GetId(), e.what());
						}
					}
					else
					{
						NetworkPacket* tmp = new NetworkPacket();
						tmp->CopyFrom(packet);
						boost::thread async(boost::bind( &TypeDelegate<T>::AsyncInvoke, this, tmp ));
					}
			   }

			private:
			   /**
			    * Metoda uruchamiana jako wątek woła metodę która obsłużyć pakiet asynchronicznie.
			    * @param packet pakiet do obsłużenia
			    */
				void AsyncInvoke(const NetworkPacket *packet)
				{
					try{
						(m_t.*m_pfn)(*packet);
					} catch(std::exception &e)
					{
						ERR("EXCEPTION(PacketID:%d) : %s", packet->GetId(), e.what());
					}
					delete packet;
				}

			private:
			   T &m_t;
			   bool m_async;
			   void (T::*m_pfn)(const NetworkPacket &);
			};

			socket_ptr m_socket;
			boost::thread *m_thread;
			boost::mutex m_mutex;
			std::map<uint16_t, delegate_ptr> m_handlers;
			std::map<uint32_t, TResponseWaitPacket* > m_waitList;
			uint32_t m_seq;
			bool 	 m_stop;
			std::set<uint32_t> m_unique;
			uint32_t m_uniqueLimit;
			NetworkProtocol **m_refBack;
		protected:
			/**
			 * Metoda dodaje nową metodę obsługująca do listy handler'ów.
			 * @param id identyfikator pakietu
			 * @param pfn metoda która obsługuje pakiet
			 * @param async true - przetwarzanie asynchroniczne
			 * @return metoda zwraca true jeśli handler został zarejestrowany.
			 */
			template<typename T>
			bool AddHandler(uint16_t id, void (T::*pfn)(const NetworkPacket &), bool async = false)
			{
					FUNCTION_TRACK
					boost::lock_guard<boost::mutex> lock(m_mutex);
					if (m_handlers.find(id)!=m_handlers.end())
						return false;

					m_handlers[id] = new TypeDelegate<T>(dynamic_cast<T*>(this), pfn, async);
					return true;
			}
			/**
			 * Metoda wyrejestrowuje obsługę danego pakietu.
			 * @param id id pakietu
			 * @return metoda zwraca true jeśli jakiś handler został wyrejestrowany, w przeciwnym wypadku false.
			 */
			bool RemoveHandler(uint16_t id);

			/**
			 * Metoda przetwarza dany pakiet sprawdzając czy jest na niego zarejestrowany handller i wołając go.
			 * @param packet pakiet do przetworzenia.
			 * @return true jesli pakiet został przetworzony lub zlecony do przetworzenia.
			 */
			bool DispatchPacket(const NetworkPacket &packet);
			/**
			 * Handler dla pakietów typu PACKET_SYNC używany w celu podtrzymania połączenia jak i dla sprawdzania jego spójności. Przy niszczeniu clienta wysyłany jest pakiet który ma zazadanie zamknąć połączenie z drugiej strony, ta metoda inicjuje zamykanie tego połączenia.
			 * @param request pakiet do przetworzenia.
			 */
			void OnSync(const NetworkPacket &request);
		public:
			/**
			 * Metoda uruchamiana jako osobny wątek, w pętli aż do przerwania sprawdza czy jakieś dane oczekują na przetworzenie w buforze połączenia, pobiera je, deserializuje z powrotem w obiekt typu NetworkPacket i zleca przetworzenie danego pakietu do metody DispatchPacket.
			 */
			void run();
			/**
			 * Metoda powstała na potrzeby workaround problemu z synchronizacją podczas niszczenia obiektu tej klasy.
			 * @param refBack pointer na zmienną zawierającą wskaźnik do tego obiektu.
			 */
			inline void SetRefBack(NetworkProtocol **refBack) { m_refBack = refBack; }
			/**
			 * Metoda sprawdza czy wykonywanie wątku obsługującego kolejkę zostało zakończone lub czy wywołano metodę Stop.
			 * @return true lub false
			 */
			inline bool IsStop() { return m_stop; }
			/**
			 * Metoda powiadamia wątek obsługujący kolejkę pakietów (metoda run()) że po przetworzeniu nastepnego pakietu ma zakończyć działanie.
			 */
			inline void Stop() { m_stop = true; }

			/**
			 * Metoda tworzy pakiet z informacją o wyjątku i wysyła go jako odpowiedź na request do nadawcy. Nadawca automatycznie zgłosi żądany wyjątek.
			 * @param exception identyfikator wyjątku
			 * @param message wiadomość jaka ma zostać wyświetlona
			 * @param request pakiet na którego odpowiedź ma zostać zgłoszony wyjątek.
			 * @return true jeśli wyjątek został wysłany, false jeśli wystapił jakiś problem lub metoda została źle użyta.
			 */
			bool SendExceptionResponse(EExceptionId exception, const std::string &message, const NetworkPacket& request);

			/**
			 * Konstruktor podstawowy dla protokołu.
			 * @param s wskaźnik na socket który ma zostać użyty do obsługi połączenia
			 * @param uniqueLimit parametr ignorowany, używany przez SendUnique
			 */
			NetworkProtocol(socket_ptr s, uint32_t uniqueLimit = 0);
			/**
			 * Domyślny destruktor, wymusza przerwanie połączenia.
			 */
			virtual ~NetworkProtocol();

			/**
			 * Metoda wysyła pakiet.
			 * @param packet pakiet do wysłania.
			 * @return True jeśli pakiet został poprawnie wysłany.
			 */
			bool SendPacket(const NetworkPacket &packet);
			/**
			 * Metoda wysyła pakiet jesli w ciągu ostatnich "uniqueLimit" pakietów identyczny pakiet nie został wysłany.
			 * @param packet pakiet do wysłania.
			 * @return True jesli pakiet został rzeczywiście wysłany.
			 */
			bool SendPacketUnique(const NetworkPacket &packet);
			/**
			 * Metoda wysyła pakiet i czeka na odpowiedź wysłaną jako exception lub przy użyciu SendPacket.
			 * @param packet pakiet do wysłania,
			 * @param response referencja na obiekt do którego ma zostać zapisana odpowiedź.
			 * @param wait_time_in_ms ilośc milisekund jaka ma oczekiwać na odpowiedź, domyślnie 24h.
			 * @return true jeśli pakiet został wysłany i otrzymaliśmy odpowiedź.
			 */
			bool SendPacketWait(const NetworkPacket &packet, NetworkPacket &response, uint32_t wait_time_in_ms = 1000*60*60*24 /*24h*/);
	};
};
#endif /*NETWORK_PROTOCOL_H*/
